<?php


namespace app\lunwen\service;

use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use think\response\Json;


class ExcelService
{
    /**
     * 写入Excel表格，可用于导出数据以及提供导入模板
     * @param string $name [表格文件名]
     * @param array $head [表头，一维数组]
     * @param array $example_data [示例数据，一维数组]
     * @param array $data [表格数据，二维数组]
     * @return Json
     * @throws Exception
     */
    public function outData($name='导出表', $head=[] ,$example_data=[] ,$data=[]){
        $spreadsheet = new Spreadsheet();   // 初始化表格实例
        $sheet = $spreadsheet->getActiveSheet();// 获得活动的表格实例

        // 写入数据
            // 写入表头的数组
        try {
            $sheet->fromArray($head);
        } catch (Exception $e) {
            return Json([
                'code'=>-2,
                'msg'=>'导出异常，错误信息：'.$e->getMessage(),
            ]);
        }
            // 写入示例数据的数组
        try {
            $sheet->fromArray($example_data, NULL, 'A3');
        } catch (Exception $e) {
            return Json([
                'code'=>-2,
                'msg'=>'导出异常，错误信息：'.$e->getMessage(),
            ]);
        }
            // 写入导入数据的数组
        try {
            $sheet->fromArray($data, NULL, 'A5');
        } catch (Exception $e) {
            return Json([
                'code'=>-2,
                'msg'=>'导出异常，错误信息：'.$e->getMessage(),
            ]);
        }

        // 写入完成后获取最大行、列
        //$highestRow = $sheet->getHighestRow();  //获取最大行
        $highestColumn = $sheet->getHighestColumn();    //获取最大列

        // 合并单元格并居中写入”示例数据、真实数据“的提示
        $styleArray = [
            'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
        ];
        $sheet->mergeCells('A2:'.$highestColumn.'2')
              ->setCellValue('A2',"示例数据")
              ->getStyle('A2')
              ->applyFromArray($styleArray);
        $sheet->mergeCells('A4:'.$highestColumn.'4')
              ->setCellValue('A4',"真实数据")
              ->getStyle('A4')
              ->applyFromArray($styleArray);

        // 设置自动列宽大小（根据第一行数据判断），无自动行高的功能
        $sheet->getDefaultColumnDimension()->setAutoSize(true);

        // 开始保存为文件，使用 try catch 做异常捕获
        try {
            // 实例化保存文件的类
            $writer = IOFactory::createWriter($spreadsheet, "Xlsx");
        } catch (\PhpOffice\PhpSpreadsheet\Writer\Exception $e) {
            // 写入专用异常捕获类，不采用导入引用，而是限定符引用
            // 异常捕获并释放资源
            $spreadsheet->disconnectWorksheets();
            unset($spreadsheet);
            // 退出执行并返回异常信息给LayUI展示
            return Json([
                'code'=>-2,
                'msg'=>'导出异常，错误信息：'.$e->getMessage(),
            ]);
        }

        /*// 指定位置储存在服务器上
        if(!is_dir("/public/upload/excel/")){
            // 检测目录是否存在，不存在则创建
            mkdir("/public/upload/excel/");
        }
        // 开始写入
        $writer->save("/public/upload/excel/".$name."xlsx");*/

        // 如果需要直接输出文件不储存在服务器上，则需要在 save 前设置 HTTP header 信息
        header('Content-Type: application/vnd.ms-excel'); // 设置文件类型
        header('Content-Disposition: attachment;filename="'.$name.'.xlsx"'); // 设置文件名称及文件后缀
        header('Cache-Control: max-age=0'); // 设置缓存时间，此处为0不缓存

        // 不储存在服务器上，直接输出在缓冲区提供下载
        try {
            $writer->save('php://output');
        } catch (\PhpOffice\PhpSpreadsheet\Writer\Exception $e) {
            return Json([
                'code'=>-2,
                'msg'=>'导出异常，错误信息：'.$e->getMessage(),
            ]);
        }

        // 正常执行后释放资源
        $spreadsheet->disconnectWorksheets();
        unset($spreadsheet);
        exit(); //结束，解决缓冲区写入额外数据导致表格错误，其他地方如非必要避免使用
    }


    /**
     * 读取 Excel 表格类，仅用于数据的读取，导入数据库逻辑应在对应表中定义
     * 最终会返回一个PHP数组
     * @param string $file
     * @return array
     */
    public function importData(string $file = ''): array {
        // 禁用读取缓存
        Calculation::getInstance()->setCalculationCacheEnabled(FALSE);

        // 设置 ValueBinder 优化读取数据格式
        Cell::setValueBinder(new AdvancedValueBinder());

        // 尝试加载文件
        try {
            $reader = IOFactory::createReaderForFile($file);
        } catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
            // 读取错误信息返回.
            return [
                'code'=>-2,
                'msg'=>'导出异常，错误信息：'.$e->getMessage(),];
        }
        $reader->setReadDataOnly(true); //设置只读加快读取速度
        $spreadsheet = $reader->load($file);  // 初始化带数据的表格类的实例
        $sheet = $spreadsheet->getActiveSheet();    // 获得活动的表格实例

        // 读取成功后，获取最大的行列，用于后续指定数据范围
        $highestRow = $sheet->getHighestRow();  //获取最大行
        $highestColumn = $sheet->getHighestColumn();    //获取最大列

        // 通过数据范围获取表格数据，返回数组
        return $spreadsheet->getActiveSheet()
                           ->rangeToArray(
                               'A5:'.$highestColumn.$highestRow, // 从A5开始读取数据
                               NULL, // 如果读取到NULL则该单元格置空
                               TRUE, // 是否计算公式，TRUE返回值，FALSE返回公式本身
                               TRUE, // 是否对单元格进行统一的格式化
                               FALSE);  // 是否按照单元格所在的行、列来索引数组的键（key）
    }
}